mod engine;

use engine::baidu;

use crate::tool::Tool;
use tool_macro::tool;
use serde_json::{json, Value};
use anyhow::{Context, Result};
use reqwest::Client;
use scraper::{Html, Selector};
use async_trait::async_trait;

#[tool(
    struct_name = WebSearch,
    name = "web_search",
    description = r#"Search the web for real-time information about any topic.
This tool returns comprehensive search results with titles and url."#,
    args = [
        "query" : {
            "type": "string",
            "description": "The search query to submit to the search engine.",
        },
        "max_results" : {
            "type": "integer",
            "description": "The number of search results to return.",
        }
    ]
)]
pub async fn web_search(query: &str, max_results: usize) -> Result<String> {
    let results = baidu::search(query, max_results).await;
    match results {
        Ok(results) => {
            let joined = results.into_iter()
                .map(|r| r.to_string())
                .collect::<Vec<_>>()
                .join(", ");
            Ok(joined)
        }
        Err(err) => Err(err.into()),
    }
}

#[tool(
    struct_name = UrlReader,
    name = "read_url",
    description = 
r#"Fetch and extract readable content from a given URL.
Returns the content or snippet of the webpage."#,
    args = [
        "url": {
            "type": "string",
            "description": "The target URL to read content from."
        }
    ]
)]
pub async fn read_url(url: &str) -> Result<String> {
    let client = Client::new();
    // Send GET request to given url
    let response = client.get(url)
        .header("User-Agent", "Mozilla/5.0 (compatible; RustBot/1.0)")
        .send()
        .await.context("Send request")?;

    let text = response
        .text()
        .await.context("Parse reponse")?;

    // Parse text as a html 
    let document = Html::parse_document(&text);

    // Fetch body part!
    let body_selector = Selector::parse("body").unwrap();
    let content = document.select(&body_selector)
        .flat_map(|elem| elem.text())
        .collect::<Vec<_>>()
        .join(" ");

    Ok(format!("Read from {}:\n{}", url, content))
}


#[allow(unused)]
#[cfg(test)]
mod test {
    use super::*;

    #[tokio::test]
    async fn test_read_url_rust_lang() {
        let url = "https://www.rust-lang.org/";
        let result = UrlReader::read_url(url).await.unwrap();

        println!("Result:\n{}", result);
    }

    #[tokio::test]
    async fn test_read_url_city() {
        let url = "https://worldpopulationreview.com/canadian-cities/toronto";
        let result = UrlReader::read_url(url).await.unwrap();

        println!("Result:\n{}", result);
    }
}